package codec

import (
	"encoding/json"
	"reflect"

	"github.com/pkg/errors"
)

var (
	ErrorMustBePointer     = errors.New("value must be a pointer")
	ErrorWrongRequestType  = errors.New("value must be a string/[]byte/struct")
	ErrorWrongResponseType = errors.New("value must be a pointer of string/[]byte/struct")
)

type Codec interface {
	Encode(v interface{}) (msg []byte, err error)
	Decode(msg []byte, r interface{}) (err error)
}

type defaultCodec struct {
}

func NewDefaultCodec() Codec {
	return &defaultCodec{}
}

func (c *defaultCodec) Encode(v interface{}) (msg []byte, err error) {

	rf := reflect.ValueOf(v)

	switch rf.Kind() {
	case reflect.String:
		msg = []byte(v.(string))
	case reflect.Slice:
		if rf.Type().String() == "[]uint8" {
			msg = v.([]byte)
		} else {
			return nil, errors.WithStack(ErrorWrongRequestType)
		}
	case reflect.Ptr:
		if rf.Elem().Kind() != reflect.Struct {
			return nil, errors.WithStack(ErrorWrongRequestType)
		}
		fallthrough
	case reflect.Struct, reflect.Map:
		msg, err = json.Marshal(v)
		if err != nil {
			return nil, errors.Wrap(err, "convert request to Json failed")
		}
	default:
		return nil, errors.WithStack(ErrorWrongRequestType)
	}
	return
}

func (c *defaultCodec) Decode(msg []byte, r interface{}) (err error) {
	rv := reflect.ValueOf(r)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return ErrorMustBePointer
	}
	rval := rv.Elem()

	kind := rval.Kind()
	switch kind {
	case reflect.String:
		rval.SetString(string(msg))
	case reflect.Slice:
		if rval.Type().String() == "[]uint8" {
			rval.SetBytes(msg)
		} else {
			return errors.WithStack(ErrorWrongResponseType)
		}
	case reflect.Struct:
		err = json.Unmarshal(msg, r)
		if err != nil {
			return errors.Wrap(err, "convert response to Json failed")
		}
	default:
		return errors.WithStack(ErrorWrongResponseType)
	}
	return
}
